//
// Created by qiyu on 17-6-6.
//

#ifndef IGUANA_XML17_HPP
#define IGUANA_XML17_HPP
#include <string.h>
#include <algorithm>
#include <functional>
#include <cctype>
#include <boost/lexical_cast.hpp>
#include "reflection.hpp"

#define IGUANA_XML_READER_CHECK_FORWARD if (l > length) return 0; work_ptr += l; length -= l
#define IGUANA_XML_READER_CHECK_FORWARD_CHAR if(length < 1) return 0; work_ptr += 1; length -= 1
#define IGUANA_XML_HEADER "<?xml version = \"1.0\" encoding=\"UTF-8\">"

namespace iguana::xml {
        namespace detail
        {
            struct char_const
            {
                static constexpr char angle_bracket = 0x3c;				// '<'
                static constexpr char anti_angle_bracket = 0x3e;		// '>'
                static constexpr char slash = 0x2f;					// '/'
                static constexpr char space = 0x20;					// ' '
                static constexpr char horizental_tab = 0x09;			// '/t'
                static constexpr char line_feed = 0x0a;					// '/n'
                static constexpr char enter = 0x0d;					// '/r'
                static constexpr char quote = 0x22;					// '"'
                static constexpr char underline = 0x5f;					// '_'
                static constexpr char question_mark = 0x3f;				// '?'
            };

            inline bool expected_char(char const* str, char character)
            {
                if (*str == character)
                    return true;
                return false;
            }

            template <typename Pred>
            inline size_t forward(char const* begin, size_t length, Pred p)
            {
                auto work_ptr = begin;
                for (size_t traversed = 0; traversed < length; traversed++)
                {
                    char work = *work_ptr;
                    if (!p(work))
                    {
                        ++work_ptr;
                    }
                    else
                    {
                        return traversed;
                    }
                }

                return length;
            }

            inline size_t ignore_blank_ctrl(char const* begin, size_t length)
            {
                return forward(begin, length, [](auto c) { return !std::isblank(c) && !std::iscntrl(c); });
            }

            inline size_t get_token(char const* str, size_t length)
            {
                if (!(std::isalpha(*str) || expected_char(str++, char_const::underline)))
                    return 0;

                return forward(str, length - 1, [](auto c) { return !std::isalnum(c) && char_const::underline != c; });
            }

            inline bool expected_token(char const* str, size_t length, char const* expected, size_t expected_length)
            {
                if (expected_length != length)
                    return false;

                if (std::strncmp(str, expected, length) == 0)
                    return true;
                return false;
            }

            inline size_t forward_until(char const* str, size_t length, char until_c)
            {
                return forward(str, length, [until_c](auto c) { return until_c == c; });
            }

            inline size_t forward_after(char const* str, size_t length, char after_c)
            {
                auto l = forward_until(str, length, after_c);
                if (l < length)
                    l++;
                return l;
            }

            template<typename T>
            constexpr bool is_64_v = std::is_same_v<T, std::int64_t>||std::is_same_v<T, std::uint64_t>;

            template <typename T>
            inline auto get_value(char const* str, size_t length, T& value)
            -> std::enable_if_t<std::is_arithmetic<T>::value>
            {
                using U = std::remove_const_t<std::remove_reference_t<T>>;
                if constexpr(std::is_integral_v<U>&&!detail::is_64_v<U>){
                    value = std::atoi(str);
                }
                else if constexpr(std::is_floating_point_v<U>){
                        value = std::atof(str);
                }
                else if constexpr(detail::is_64_v<U>){
                    value = std::atoll(str);
                }
                else{
                    std::cout<<"don't support the type now"<<std::endl;
                    throw(std::invalid_argument("don't support the type now"));
                }
            }

            void get_value(char const* str, size_t length, std::string& value)
            {
                value.assign(str, length);
            }

            template <typename T>
            struct array_size
            {
                static constexpr size_t value = 0;
            };

            template <typename T, size_t Size>
            struct array_size<T const(&)[Size]>
            {
                static constexpr size_t value = Size;
            };
        }

        class xml_reader_t
        {
            enum object_status
            {
                EMPTY = -1,
                ILLEGAL = 0,
                NORMAL = 1,
            };

        public:
            xml_reader_t(char const* buffer, size_t length)
                    : buffer_(buffer)
                    , length_(length)
            {
            }

            bool get_root()
            {
                auto work_ptr = buffer_;
                auto length = length_;

                auto l = detail::ignore_blank_ctrl(work_ptr, length);
                IGUANA_XML_READER_CHECK_FORWARD;

                if (!detail::expected_char(work_ptr, detail::char_const::angle_bracket))
                    return false;
                IGUANA_XML_READER_CHECK_FORWARD_CHAR;

                if (!detail::expected_char(work_ptr, detail::char_const::question_mark))
                    return false;
                IGUANA_XML_READER_CHECK_FORWARD_CHAR;

                l = detail::get_token(work_ptr, length);
                if (!detail::expected_token(work_ptr, l, "xml", 3))
                    return false;
                IGUANA_XML_READER_CHECK_FORWARD;

                l = detail::forward_after(work_ptr, length, detail::char_const::anti_angle_bracket);
                IGUANA_XML_READER_CHECK_FORWARD;

                buffer_ = work_ptr;
                length_ = length;

                return true;
            }

            int begin_object(char const* expected)
            {
                auto work_ptr = buffer_;
                auto length = length_;

                auto l = detail::ignore_blank_ctrl(work_ptr, length);
                IGUANA_XML_READER_CHECK_FORWARD;

                if (!detail::expected_char(work_ptr, detail::char_const::angle_bracket))
                    return object_status::ILLEGAL;
                IGUANA_XML_READER_CHECK_FORWARD_CHAR;

                l = detail::get_token(work_ptr, length);
                if (!detail::expected_token(work_ptr, l, expected, std::strlen(expected)))
                    return false;
                IGUANA_XML_READER_CHECK_FORWARD;

                l = detail::forward_after(work_ptr, length, detail::char_const::anti_angle_bracket);
                IGUANA_XML_READER_CHECK_FORWARD;

                buffer_ = work_ptr;
                length_ = length;

                if (detail::expected_char(work_ptr - 1, detail::char_const::slash))
                    return object_status::EMPTY;
                return object_status::NORMAL;
            }

            template <typename T>
            bool get_value(T& t)
            {
                auto work_ptr = buffer_;
                auto length = length_;

                auto l = detail::ignore_blank_ctrl(work_ptr, length);
                IGUANA_XML_READER_CHECK_FORWARD;

                l = detail::forward_until(work_ptr, length, detail::char_const::angle_bracket);
                try
                {
                    detail::get_value(work_ptr, l, t);
                }
                catch (...)
                {
                    return false;
                }
                IGUANA_XML_READER_CHECK_FORWARD;

                buffer_ = work_ptr;
                length_ = length;

                return true;
            }

            bool end_object(char const* expected)
            {
                auto work_ptr = buffer_;
                auto length = length_;

                auto l = detail::ignore_blank_ctrl(work_ptr, length);
                IGUANA_XML_READER_CHECK_FORWARD;

                if (!detail::expected_char(work_ptr, detail::char_const::angle_bracket))
                    return false;
                IGUANA_XML_READER_CHECK_FORWARD_CHAR;

                if (!detail::expected_char(work_ptr, detail::char_const::slash))
                    return false;
                IGUANA_XML_READER_CHECK_FORWARD_CHAR;

                l = detail::get_token(work_ptr, length);
                if (!detail::expected_token(work_ptr, l, expected, std::strlen(expected)))
                    return false;
                IGUANA_XML_READER_CHECK_FORWARD;

                if (!detail::expected_char(work_ptr, detail::char_const::anti_angle_bracket))
                    return false;
                IGUANA_XML_READER_CHECK_FORWARD_CHAR;

                buffer_ = work_ptr;
                length_ = length;
                return true;
            }

            static const size_t xml_header_length = detail::array_size<decltype(IGUANA_XML_HEADER)>::value - 1;

        private:
            char const*	buffer_;
            size_t		length_;
        };

        //to xml
        template<typename Stream, typename T>
        std::enable_if_t<!std::is_floating_point<T>::value && (std::is_integral<T>::value || std::is_unsigned<T>::value || std::is_signed<T>::value)>
        render_xml_value(Stream& ss, T value)
        {
            char temp[20];
            auto p = itoa_fwd(value, temp);
            ss.write(temp, p - temp);
        }

        template<typename Stream, typename T>
        std::enable_if_t<std::is_floating_point<T>::value> render_xml_value(Stream& ss, T value)
        {
            char temp[20];
            sprintf(temp, "%f", value);
            ss.write(temp);
        }

        template<typename Stream>
        void render_xml_value(Stream& ss, const std::string &s)
        {
            ss.write(s.c_str(), s.size());
        }

        template<typename Stream>
        void render_xml_value(Stream& ss, const char* s)
        {
            ss.write(s, strlen(s));
        }

        template<typename Stream, typename T>
        std::enable_if_t<std::is_arithmetic<T>::value> render_key(Stream& ss, T t) {
            ss.put('<');
            render_xml_value(ss, t);
            ss.put('>');
        }

        template<typename Stream>
        void render_key(Stream& ss, const std::string &s) {
            render_xml_value(ss, s);
        }

        template<typename Stream>
        void render_key(Stream& ss, const char* s) {
            render_xml_value(ss, s);
        }

        template<typename Stream>
        void render_tail(Stream& ss, const char* s)
        {
            ss.put('<');
            ss.put('/');
            ss.write(s, strlen(s));
            ss.put('>');
        }

        template<typename Stream>
        void render_head(Stream& ss, const char* s)
        {
            ss.put('<');
            ss.write(s, strlen(s));
            ss.put('>');
        }

        template<typename Stream, typename T, typename = std::enable_if_t<is_reflection<T>::value>>
        void to_xml_impl(Stream& s, T &&t) {
            for_each(std::forward<T>(t), [&t, &s](const auto v,  auto i)
            {
                using M = decltype(iguana_reflect_members(std::forward<T>(t)));
                constexpr auto Idx = decltype(i)::value;
                constexpr auto Count = M::value();
                static_assert(Idx<Count);

				using type_v = decltype(std::declval<T>().*std::declval<decltype(v)>());
                if constexpr (!is_reflection<type_v>::value)
                {
                    render_head(s, get_name<T, Idx>().data());
                    render_xml_value(s, t.*v);
                    render_tail(s, get_name<T, Idx>().data());
                }
                else
                {
                    render_head(s, get_name<T, Idx>().data());
                    to_xml_impl(s, t.*v);
                    render_tail(s, get_name<T, Idx>().data());
                }
            });
        }

        template<typename Stream, typename T, typename = std::enable_if_t<is_reflection<T>::value>>
        void to_xml(Stream& s, T &&t) {
            //render_head(s, "xml");
            s.write(IGUANA_XML_HEADER, xml_reader_t::xml_header_length);
            to_xml_impl(s, std::forward<T>(t));
        }

        template<typename T, typename = std::enable_if_t<is_reflection<T>::value>>
        constexpr void do_read(xml_reader_t &rd, T &&t)
        {
            for_each(std::forward<T>(t), [&t, &rd](const auto v,  auto i)
            {
                using M = decltype(iguana_reflect_members(std::forward<T>(t)));
                constexpr auto Idx = decltype(i)::value;
                constexpr auto Count = M::value();
                static_assert(Idx<Count);

				using type_v = decltype(std::declval<T>().*std::declval<decltype(v)>());
                if constexpr (!is_reflection<type_v>::value)
                {
                    if (rd.begin_object(get_name<T, Idx>().data()) == 1)
                    {
                        // read value
                        rd.get_value(t.*v);
                        rd.end_object(get_name<T, Idx>().data());
                    }
                }
                else
                {
                    if (rd.begin_object(get_name<T, Idx>().data()) == 1)
                    {
                        do_read(rd, t.*v);
                        rd.end_object(get_name<T, Idx>().data());
                    }
                }
            });
        }

        template<typename T, typename = std::enable_if_t<is_reflection<T>::value>>
        void from_xml(T &&t, const char *buf, size_t len = -1)
        {
            xml_reader_t rd(buf, len);
            if (rd.get_root())
            {
                do_read(rd, t);
            }
        }
    }
#endif //IGUANA_XML17_HPP
